// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';

import 'package:meta/meta.dart';

/// The [EntityCodec] is a typed [Codec] which translates
/// entity data between the [Uint8List] wire format and a
/// dart object.
///
/// [EntityCodec]s have a specific semantic type and encoding
/// which allow them to operate with the entity system.
abstract class EntityCodec<T> extends Codec<T, Uint8List> {
  /// The semantic type of the entity which this codec supports.
  /// This type will be used when requesting entities from the
  /// framework.
  final String type;

  /// The encoding that this [EntityCodec] knows how to encode/decode data
  final String encoding;

  /// The default constructor.
  const EntityCodec({
    @required this.type,
    @required this.encoding,
  })  : assert(type != null),
        assert(encoding != null);
}

/// A [Codec] used for handling the the automatic translation to and from an
/// Entity's source data to the specified Dart type [T].
class SimpleEntityCodec<T> extends EntityCodec<T> {
  final _EntityEncoder<T> _encoder;
  final _EntityDecoder<T> _decoder;

  /// Create a new [EntityCodec].
  SimpleEntityCodec({
    @required String type,
    @required String encoding,
    @required _EncodeEntity<T> encode,
    @required _DecodeEntity<T> decode,
  })  : assert(type != null),
        assert(encoding != null),
        assert(encode != null),
        assert(decode != null),
        _encoder = _EntityEncoder<T>(encode),
        _decoder = _EntityDecoder<T>(decode),
        super(type: type, encoding: encoding);

  @override
  _EntityEncoder<T> get encoder => _encoder;

  @override
  _EntityDecoder<T> get decoder => _decoder;
}

typedef _EncodeEntity<T> = Uint8List Function(T value);

class _EntityEncoder<T> extends Converter<T, Uint8List> {
  final _EncodeEntity<T> encode;

  const _EntityEncoder(this.encode);

  @override
  Uint8List convert(T source) {
    return encode(source);
  }
}

typedef _DecodeEntity<T> = T Function(Uint8List data);

class _EntityDecoder<T> extends Converter<Uint8List, T> {
  final _DecodeEntity<T> decode;

  const _EntityDecoder(this.decode);

  @override
  T convert(Uint8List data) => decode(data);

  @override
  Stream<T> bind(Stream<Uint8List> source) {
    _EntityDecoderSink<T> map(EventSink<T> out) =>
        new _EntityDecoderSink<T>(out, this);

    return new Stream<T>.eventTransformed(source, map);
  }
}

/// Entity data [String]s in, [T] out.
class _EntityDecoderSink<T> extends EventSink<Uint8List> {
  /// The [EventSink] that values are decoded into. Errors generated by the
  /// decoder are also added to [out].
  final EventSink<T> out;

  /// The decoder to used to convert the source ([Stream<String>]) events.
  final _EntityDecoder<T> decoder;

  /// Create an instance of [_EntityDecoderSink], usually via the
  /// [Stream#eventTransformed] constructor.
  _EntityDecoderSink(this.out, this.decoder);

  @override
  void add(Uint8List data) {
    try {
      T value = decoder.decode(data);
      out.add(value);
    } on Object catch (err, stackTrace) {
      addError(err, stackTrace);
    }
  }

  @override
  void addError(Object e, [StackTrace s]) => out.addError(e, s);

  @override
  void close() => out.close();
}
